<template><div><h1 id="多线程" tabindex="-1"><a class="header-anchor" href="#多线程"><span>多线程</span></a></h1>
<h2 id="线程" tabindex="-1"><a class="header-anchor" href="#线程"><span>线程</span></a></h2>
<h4 id="_1-线程状态" tabindex="-1"><a class="header-anchor" href="#_1-线程状态"><span>1. 线程状态</span></a></h4>
<p>线程是CPU任务调度的最小执行单位，每个线程拥有自己独立的程序计数器、虚拟机栈、本地方法栈。
<strong>线程状态：新建、就绪、运行、等待、阻塞、终止</strong></p>
<h4 id="_2-线程状态的切换" tabindex="-1"><a class="header-anchor" href="#_2-线程状态的切换"><span>2. 线程状态的切换</span></a></h4>
<p>start
run
sleep
wait
notify
notifyAll
yield
join</p>
<h4 id="_3-阻塞唤醒过程" tabindex="-1"><a class="header-anchor" href="#_3-阻塞唤醒过程"><span>3. 阻塞唤醒过程</span></a></h4>
<p><strong>阻塞：</strong>
<strong>唤醒：</strong></p>
<h4 id="_4-wait和sleep的区别" tabindex="-1"><a class="header-anchor" href="#_4-wait和sleep的区别"><span>4. wait和sleep的区别</span></a></h4>
<h4 id="_5-创建线程的方式" tabindex="-1"><a class="header-anchor" href="#_5-创建线程的方式"><span>5. 创建线程的方式</span></a></h4>
<ul>
<li>实现Runnable接口</li>
<li>继承Thread类</li>
<li>实现Collable接口</li>
<li>使用线程池</li>
</ul>
<h2 id="线程池" tabindex="-1"><a class="header-anchor" href="#线程池"><span>线程池</span></a></h2>
<p>优点：通过复用已创建的线程，<strong>降低资源损耗</strong>，线程可以直接处理队列中的任务，<strong>加快响应速度</strong>，同时便于<strong>统一监控和管理</strong>。</p>
<h4 id="_1-线程池参数" tabindex="-1"><a class="header-anchor" href="#_1-线程池参数"><span>1. 线程池参数</span></a></h4>
<ol>
<li>corePoolSize：核心线程池大小；</li>
<li>maximumPoolSize：最大线程池大小；</li>
<li>keepAliveTime：线程池中超过corePoolSize数目的空闲线程最大存活时间；</li>
<li>TimeUnit：KeepAliveTime的时间单位；</li>
<li>workQueue：阻塞任务队列；</li>
<li>threadFactory：新建线程的工厂；</li>
<li>RejectedExecutionHandler：拒绝策略。当提交任务数超过maximumPoolSize+workQueue之和时，任务交给RejectedExecutionHandler来处理；</li>
</ol>
<h4 id="_2-线程处理任务过程" tabindex="-1"><a class="header-anchor" href="#_2-线程处理任务过程"><span>2. 线程处理任务过程</span></a></h4>
<ol>
<li>当线程池小于corePoolSize时，新提交的任务将创建一个新的线程执行任务，即使此时线程池中存在空闲线程。</li>
<li>当线程池达到corePoolSize时，新提交的任务将被放入workQueue中，等待线程池中任务调度执行。</li>
<li>当workQueue已满，且maximumPoolSize大于corePoolSize时，新提交任务会创建新线程执行任务。</li>
<li>当提交任务数超过maximumPoolSize时，新提交任务由RejectedExecutionHandler来处理</li>
<li>当线程池中超过corePoolSize线程，空闲时间达到keepAliveTime时，关闭空闲的线程。</li>
</ol>
<h4 id="_3-线程拒绝策略" tabindex="-1"><a class="header-anchor" href="#_3-线程拒绝策略"><span>3. 线程拒绝策略</span></a></h4>
<p>当线程池中的线程被用完了，且等待队列也已经满的时候，就需要通过拒绝策略机制合理的处理无法执行任务这个问题。
JDK内置的拒绝策略如下：
<strong>AbortPolicy：</strong> 直接抛出异常，阻止系统正常运行。可以根据业务逻辑选择重试或者放弃提交等策略。
<strong>CallerRunsPolicy：</strong> 只要线程池未关闭，该策略直接在调用者线程中，运行当前被丢弃的任务。不会造成任务丢失，同时减缓提交任务的速度，给执行任务缓冲时间。
<strong>DiscardOldestPolicy：</strong> 丢弃最老的一个请求，也就是即将被执行的任务，并尝试再次提交当前任务。
<strong>DiscardPolicy：</strong> 该策略默默地丢弃无法处理的任务，不予任何处理。如果允许任务丢失，这是最好的一种方案。</p>
<h4 id="_4-executors类实现线程池" tabindex="-1"><a class="header-anchor" href="#_4-executors类实现线程池"><span>4. Executors类实现线程池</span></a></h4>
<ul>
<li>newSingleThreadExecutor():只有一个线程的线程池，任务是顺序执行，适用于一个一个任务执行的场景。</li>
<li>newCachedThreadPool()：线程池里有很多线程需要同时执行。60s内复用，使用执行很多短期异步的小程序或者负载较轻的服务。</li>
<li>newFixedThreadPool()：拥有固定线程数的线程池，如果没有任务执行，那么线程会一直等待，适用执行长期的任务。</li>
<li>newScheduledThreadPool()：用来调度即将执行的任务的线程池。</li>
<li>newWorkStealingPool()：底层采用forkjoin的Deque，采用独立的任务队列可以减少竞争同时加快任务处理。</li>
</ul>
<p><strong>总结：</strong>
SingleThreadExecutor和FixedThreadPool：允许请求的<strong>队列长度</strong>为Integer.MAX_VALUE，会导致OOM。
CachedThreadPool和wWorkStealingPool：允许创建的<strong>线程数量</strong>为Integer.MAX_VALUE，会导致OOM。
手动创建的线程池底层使用的是<code v-pre>ArrayBlockingQueue</code>，可以<strong>防止OOM</strong>。</p>
<h4 id="_5-线程池大小设置" tabindex="-1"><a class="header-anchor" href="#_5-线程池大小设置"><span>5. 线程池大小设置</span></a></h4>
<p>以下的<strong>n</strong>代表的是<strong>CPU核心数</strong></p>
<ul>
<li>CPU密集型（n+1）
CPU密集的意思是该任务需要大量的运算，而没有阻塞，CPU一直全速运行。
CPU密集型任务尽可能的少的线程数量，一般为CPU核数+1个线程的线程池。</li>
<li>IO密集型（2n）
由于IO密集型任务线程并不是一直在执行任务，可以多分配一些线程数，如CPU核数*2。
也可以使用公式：CPU核心数*（1+平均等待时间/平均工作时间）。</li>
</ul>
<h2 id="线程安全" tabindex="-1"><a class="header-anchor" href="#线程安全"><span>线程安全</span></a></h2>
<h4 id="_1-乐观锁、cas思想" tabindex="-1"><a class="header-anchor" href="#_1-乐观锁、cas思想"><span>1. 乐观锁、CAS思想</span></a></h4>
<p><strong>java乐观锁机制：</strong>
它是一种积极的思想，总是认为数据是不会被修改的，所以是不会对数据上锁。但是乐观锁在更新数据的时候会去判断数据是否被更新过。</p>
<p>乐观锁的实现方案一般有两种：<strong>版本号机制</strong>和<strong>CAS</strong>。
乐观锁适用于<strong>读多写少的场景，这样可以提高系统的并发量</strong>。在Java中<code v-pre>java.util.concurrent.atomic</code>下的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。</p>
<p>乐观锁，大多数基于数据版本（Version）记录机制实现。即为数据增加一个版本标识，也就是说在数据库表增加一个&quot;version&quot;字段来实现。读取数据时，将version一同读出，在之后的更新中，对此version+1.此时，将提交数据的version与数据库表对应记录的version信息进行比对，如果提交数据的version大于数据库表当前的version，则予以更新，否则认为是过期数据。</p>
<p><strong>CAS思想：</strong>
CAS就是Compare And Swap（比较交换），是一种很出名的无锁的算法，就是可以不适用锁机制实现线程间的同步。使用CAS的线程是不会被阻塞的，所以又称为非阻塞同步。</p>
<p>CAS算法涉及到三个操作：
需要读写内存值V；进行比较的值A；准备写入的值B
当且仅当V的值等于A的值的时候，采用B的值去更新V的值，否则不会执行任何操作（比较和替换是一个<strong>原子操作</strong>，A和V比较，V和B替换），一般情况下是一个<strong>自旋</strong>操作，即不断重试。</p>
<p>缺点：https://www.zhihu.com/question/23281499/answer/854522984
ABA问题：高并发情况下，很容易发生并发冲突，如果CAS一直失败，那么就会一直重试，浪费CPU资源。</p>
<p><strong>原子性：</strong> 功能限制CAS是能保证单个变量是原子性的，在Java中要配合使用volatile关键字来保证线程的安全；当涉及到多个变量的时候，CAS无能为力；除此之外，CAS实现需要硬件层面的支持，在Java的普通用户中无法直接使用，只能借助atomic包下的原子类实现，灵活性受到了限制。</p>
<h4 id="_2-synchronized底层实现" tabindex="-1"><a class="header-anchor" href="#_2-synchronized底层实现"><span>2. synchronized底层实现</span></a></h4>
<p>使用方法：</p>
<ul>
<li><strong>修饰实例方法：</strong> 作用于当前对象实例加锁，进入同步代码前要或得当前对象实例的锁。</li>
<li><strong>修饰静态方法：</strong> 也就是给当前类加锁，会作用于类的所有对象实例，因为静态成员不属于任何一个实例对象，是类成员。</li>
<li><strong>修饰代码块：</strong> 执行加锁对象，对给定对象加锁，进入同步代码块之前要获取给定对象的锁。</li>
</ul>
<p><strong>总结：</strong> synchronized锁住的资源只有两类：一个是<strong>对象</strong>，一个是<strong>类</strong>。</p>
<p><strong>底层实现：</strong>
对象头是我们需要关注的重点，它是synchronized实现锁的基础。因为synchronized申请锁、上锁、释放锁都与对象头有关。对象头主要结构是有<strong>Mark Word</strong>组成，其中<strong>Mark Word存储对象的hashCode、所信息或分代年龄或GC标志等信息</strong>。
锁也分不同状态，JDK6之前只有两种状态：无锁、有锁（重量级锁），而在JDK6之后对synchronized进行了优化，新增了两种状态，总共就是四个状态：<strong>无锁状态、偏向锁、轻量级锁、重量级锁</strong>，其中无锁就是一种状态了。锁的类型和状态在<strong>对象头Mark Word</strong>中都有记录，在申请锁、锁升级等过程中JVM都需要读取<strong>对象头中的Mark Work</strong>数据。</p>
<p><strong>同步代码块是利用monitorenter和minitorexit指令实现的，而同步方法则是利用flags实现的。</strong></p>
<h4 id="_3-reenrantlock底层实现" tabindex="-1"><a class="header-anchor" href="#_3-reenrantlock底层实现"><span>3. ReenrantLock底层实现</span></a></h4>
<p>由于ReentrantLock是<code v-pre>java.util.concurrent</code>包下提供的一套<strong>互斥锁</strong>，相比于synchronized，ReentrantLock类提供了一些高级功能。</p>
<p><strong>使用方法：</strong>
基于API层面的互斥锁，需要lock()和unlock()方法配合try/finally语句块来完成。</p>
<p><strong>底层实现：</strong>
ReentrantLock的实现是一种自旋锁，通过循环调用CAS操作来实现加锁。它的性能比较好也是因为<strong>避免了使线程进入内核态的阻塞状态</strong>。想尽办法避免线程进入内核态的阻塞状态使我们去分析和理解锁设计的关键钥匙。</p>
<p>和<strong>synchronized</strong>的区别：</p>
<ol>
<li><strong>底层实现：</strong> synchronized是<strong>JVM层面</strong>的锁，是<strong>Java关键字</strong>，通过minitor对象来完成（monitorenter和monitorexit）指令。ReentrantLock是从JDK1.5以来(<code v-pre>java.util.concurrent.locks.Lock</code>)提供的<strong>API层面</strong>的锁。</li>
<li><strong>实现原理：</strong> synchronized的实现涉及到<strong>锁的升级</strong>，具体为<strong>无锁、偏向锁、自旋锁、像OS申请重量级锁</strong>；
ReentrantLock实现则是通过利用<strong>CAS自旋机制保证线程操作的原子性和volatile保证数据可见性</strong>以实现锁的功能。</li>
<li><strong>是否可手动释放：</strong> synchronized不需要用户手动去释放锁，synchronized代码执行完成后系统会自动让线程释放对锁的占用；
ReentrantLock则需要用户手动释放锁，如果没有手动释放锁，就有可能导致<strong>死锁现象</strong>。</li>
<li><strong>是否可中断：</strong> synchronized是不可中断类型的锁，除非加锁的代码中出现异常或正常执行完成；
ReentrantLock则可以中断，可通过<code v-pre>trylock(long timeout,TimeUnit unit)</code>设置超时方法或者将lockInterruptibly()放到代码块中，调用Interrupt方法进行中断。</li>
<li><strong>是否公平锁：</strong> synchronized为非公平锁，ReentrantLock则可以选择公平锁或者飞公平锁，通过构造方法<code v-pre>new ReentrantLock(boolean flag)</code>自行选择，为空默认false非公平锁，true为公平锁，公平锁性能非常低。</li>
</ol>
<h4 id="_4-公平锁和非公平锁的区别" tabindex="-1"><a class="header-anchor" href="#_4-公平锁和非公平锁的区别"><span>4. 公平锁和非公平锁的区别</span></a></h4>
<p><strong>公平锁：</strong>
先进先出</p>
<p><strong>非公平锁：</strong>
竞争，竞争不到就进入等待队列</p>
<h4 id="_5-使用层面的锁优化" tabindex="-1"><a class="header-anchor" href="#_5-使用层面的锁优化"><span>5. 使用层面的锁优化</span></a></h4>
<ol>
<li>减少锁的时间</li>
<li>减少锁的粒度</li>
<li>锁粗化</li>
<li>使用读写锁 ReentrantReadWriteLock</li>
<li>使用CAS</li>
</ol>
<h4 id="_6-系统层面的锁优化" tabindex="-1"><a class="header-anchor" href="#_6-系统层面的锁优化"><span>6. 系统层面的锁优化</span></a></h4>
<ol>
<li>自适应自旋锁</li>
<li>锁清除</li>
<li>锁升级 -&gt; 偏向锁、轻量级锁、重量级锁</li>
</ol>
<p>偏向锁：如果线程已经占有了这个锁，当它再次试图去获取这个锁的时候，它会以最快的方式去拿到这个锁，而不需要再进行一些monitor操作，因为在大部分情况下是没有竞争的，所以使用偏向锁是可以提高性能的。
轻量级锁：（自旋锁）在竞争不激烈的情况下，通过CAS避免线程上下文切换，可以显著的提高功能。
重量级锁：重量级锁的加锁、解锁过程造成的损耗是固定的，重量级锁适合于竞争激烈、高并发、同步代码块执行时间长的情况。</p>
<h4 id="_7-threadlocal的原理" tabindex="-1"><a class="header-anchor" href="#_7-threadlocal的原理"><span>7. ThreadLocal的原理</span></a></h4>
<p>ThreadLocal可以解释是当前线程的私有仓库，存放当前线程的本地变量。
原理：
ThreadLocal是一个泛型类，保证可以接收任何类型的对象。ThreadLocal内部维护了一个Map，是ThreadLocal实现的一个叫做ThreadLocalMap的静态内部类，专门存储一个线程内的多个ThreadLocal对象的。</p>
<p><strong>ThreadLocal内存泄露的场景</strong>
实际上ThreadLocalMap中使用的key为ThreadLocal的弱引用，而value是强引用。
弱引用的特点是，如果这个对象持有弱引用，那么在下一次垃圾回收的时候必然会被清理掉。
所以如果ThreadLocal没有被外部引用的情况下，在垃圾回收的时候会被清理掉的，这样一来ThreadLocalMap中使用这个ThreadLocal的key也会被清理掉。但是，value是强引用，不会被清理，这样一来就会出现key为null的value。假设我们不做任何措施的话，value永远无法被GC回收，如果线程长时间宝不被销毁，可能会产生<strong>内存泄露</strong>。
<img src="@source/img/thread-local.png" alt="alt text" loading="lazy">
ThreadLocalMao视线中已经考虑了这种情况，在调用set()、get()、remove()方法的时候，会清理掉key为null的记录。如果说会出现内存泄露，那么只有在出现了key为null的记录后，没有手动调用remove()方法，并且之后也不再调用set()、get()、remove()方法的情况下。因此<strong>使用完ThreadLocal方法后，最好手动调用remove()</strong> 方法。</p>
<h4 id="_8-hashmap线程安全" tabindex="-1"><a class="header-anchor" href="#_8-hashmap线程安全"><span>8. HashMap线程安全</span></a></h4>
<h4 id="_9-string不可变原因" tabindex="-1"><a class="header-anchor" href="#_9-string不可变原因"><span>9. String不可变原因</span></a></h4>
<h2 id="内存模型" tabindex="-1"><a class="header-anchor" href="#内存模型"><span>内存模型</span></a></h2>
<p>原子性
可见性
有序性</p>
<h4 id="_1-volatile底层实现" tabindex="-1"><a class="header-anchor" href="#_1-volatile底层实现"><span>1. volatile底层实现</span></a></h4>
<h4 id="_2-aqs思想" tabindex="-1"><a class="header-anchor" href="#_2-aqs思想"><span>2. AQS思想</span></a></h4>
<h4 id="_3-happens-before" tabindex="-1"><a class="header-anchor" href="#_3-happens-before"><span>3. happens-before</span></a></h4>
<p>用来描述和可见性相关问题：如果第一个操作happens-before第二个操作，那么我们就说第一个操作对于第二个操作时可见的。
常见的happens-before：volatile、锁、线程生命周期。</p>
</div></template>


